/*
 *  Copyright (C) 2012 Компания ВИТ. web: http://diasled.ru Е-mail:  vit@diasled.ru
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ru.apertum.diasled.model.load;

import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities;
import ru.apertum.diasled.common.Uses;
import ru.apertum.diasled.common.exeptions.ClientException;
import ru.apertum.diasled.common.exeptions.QException;
import ru.apertum.diasled.forms.FExamMaster;
import ru.apertum.diasled.model.Examination;
import ru.apertum.diasled.cfg.Configuration;
import ru.apertum.diasled.cfg.Outline;
import ru.apertum.diasled.redactor.SensorDraw;

/**
 * В параллельном потоке вставляем данные от сканера в БД.
 * @author Evgeniy Egorov, Aperum Projects
 */
public class Inserter implements Runnable {

    private int result = 0;

    public int getResult() {
        return result;
    }
    /**
     * Загрузка данных именно этого обследования
     */
    private final Examination exam;
    /**
     * конфигурация для обследования.
     */
    private final Configuration cfg;

    public Examination getExam() {
        return exam;
    }
    private final LinkedBlockingQueue<byte[]> queue;

    public Inserter(LinkedBlockingQueue<byte[]> queue, Examination exam) {
        this.queue = queue;
        this.exam = exam;
        cfg = Configuration.loadByID(exam.getSysNum());
        if (cfg == null) {
            throw new ClientException("Пpоблема с загрузкой конфигурации для вставки в БД.");
        }
    }
    private long start;
    private final Runnable stateUpdater = new Runnable() {

        @Override
        public void run() {
            FExamMaster.hackState.setText("<html>Прошло времени " + (new Date().getTime() - start) / 1000 + "сек.<br>Обработано фреймов " + nframe + "<br>Фреймов для обработки " + queue.size());
        }
    };

    private void showState() {
        SwingUtilities.invokeLater(stateUpdater);
    }

    @Override
    public void run() {
        start = new Date().getTime();
        final StringBuilder sb = new StringBuilder();
        try {
            Class.forName("org.h2.Driver").newInstance();
            //try (Statement stmt = DriverManager.getConnection("jdbc:mysql://127.0.0.1/hiber?autoReconnect=true&amp;characterEncoding=UTF-8", "root", "root").createStatement()) {
            try (Statement stmt = DriverManager.getConnection(Uses.DB_URL, "diasled", "diasled").createStatement()) {
                stmt.getConnection().setAutoCommit(false);
                int i = 0;
                // счетчик неполучения данных из буфера, куда сканер складывает данные, 0,2сек не было ничего и завершаем вставку
                int empty = 0;

                boolean flag = false;
                while (empty < 30 && !Thread.currentThread().isInterrupted()) {
                    byte[] frame = null;
                    try {
                        frame = queue.poll(100, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException ex) {
                        frame = null;
                    }
                    if (frame != null) {
                        if (frame.length == Generator.ERROR_ARRAY.length) { // было прислано сообщение об ошибки сбора информации
                            result = 2;
                            Uses.log.logger.trace("Rollback в БД");
                            stmt.getConnection().rollback();
                            return;
                        }
                        if (frame.length == Generator.STOP_ARRAY.length) {
                            flag = true;
                            break;
                        }
                        prepareFrame(frame, sb.append(","));
                        i++;
                    }
                    if ((frame == null || frame.length == Generator.STOP_ARRAY.length || i == FRAME_SET) && sb.length() != 0) {
                        stmt.execute("INSERT INTO SENSOR VALUES " + sb.substring(1));
                        sb.setLength(0);
                        i = 0;
                        empty = 0;
                        showState();
                    } else {
                        if (frame == null || sb.length() == 0) {
                            empty++;
                        }
                    }

                }

                //select min(sum_0) ms0, min(sum_1)  ms1, min(sum_2)  ms2 from sensor where exam_id =4
                //update sensor  set sum_0  = sum_0 -1, sum_1  = sum_1 -1 where exam_id =4
                //select avg(sum_0) as0, avg(sum_1) as1 from sensor where exam_id =4 and frame_id > 1300

                if (flag) {
                    // оформим поджатие и умножение на кормирующий коэфф.
                    final StringBuilder ss = new StringBuilder();
                    // определим коэфф. нормирования
                    for (int j = 0; j < cfg.getOutlines().size(); j++) {
                        ss.append(",avg(sum_").append(j).append(") as").append(j);
                    }
                    String sql = "SELECT " + ss.substring(1) + " FROM SENSOR where EXAM_ID=" + exam.getId() + " and frame_id>" + (nframe - 200);
                    ss.setLength(0);
                    int sumk = 0;
                    try (ResultSet rs = stmt.executeQuery(sql)) {
                        rs.next();
                        for (int j = 0; j < cfg.getOutlines().size(); j++) {
                            sumk = sumk + rs.getInt("as" + j);
                        }
                    }
                    // определим поджатие
                    for (int j = 0; j < cfg.getOutlines().size(); j++) {
                        ss.append(",min(sum_").append(j).append(") ms").append(j);
                    }
                    sql = "SELECT " + ss.substring(1) + " FROM SENSOR where EXAM_ID =" + exam.getId();
                    ss.setLength(0);
                    
                    int podj = 0;
                    try (ResultSet rs = stmt.executeQuery(sql)) {
                        rs.next();
                        for (int j = 0; j < cfg.getOutlines().size(); j++) {
                            ss.append(",sum_").append(j).append("=(sum_").append(j).append("-").append(rs.getInt("ms" + j)).append(")*").append("%");
                            podj = podj + rs.getInt("ms" + j);
                        }
                    }
                    float k = (float) exam.getVisit().getWeight() * 1000 / (float) (sumk-podj);
                    // отнормируем
                    stmt.execute("update sensor set " + ss.substring(1).replaceAll("%", "" + k) + " where EXAM_ID =" + exam.getId());
                    ss.setLength(0);


                    Uses.log.logger.trace("Commit в БД");
                    // коммитим все сразу
                    stmt.getConnection().commit();
                    result = 1;
                } else {
                    stmt.getConnection().rollback();
                    throw new QException("Чтение инфы из сканера не закончилось признаком удачного завершения.");
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | SQLException | QException except) {
            result = 2;
            Uses.log.logger.error("Не получается вставить данные сканера в БД!", except);
        } finally {
            // тут сообщить мастеру обследования что поток вставки отработал. Мастер отсчитал таймауты и ждет только окончания вставки
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    FExamMaster.WAIT_FOR_FINISH.unlock();
                }
            });
            sb.setLength(0);
        }
        Uses.log.logger.info("Закончилось вставление в БД");
    }
    /**
     * размер пачки фреймов одновременно отправляющихся в БД
     */
    private final static int FRAME_SET = 200;
    /**
     * номеруем фреймы
     */
    private long nframe = 0;
    private long oldNframe = 0;

    public boolean isWork() {
        final boolean f = nframe != oldNframe;
        oldNframe = nframe;
        return f;
    }

    private void prepareFrame(byte[] frame, StringBuilder forFrame) {
        // это уже готовые веса от сенсоров, их вставить в БД
        final int[] frBD = new int[frame.length];
        // суммы весов по контурам
        final int[] sums = new int[120];
        // бежим по всем сенсорам и формируем веса от сенсоров и суммы по контурам
        // если нет контура, то все попадают в "первый" контур
        int sumX = 0;
        int sumY = 0;
        int maxD = 0;
        for (SensorDraw sensor : cfg.getSensors()) {
            if (cfg.getOutlines().isEmpty()) {
                frBD[sensor.getLogicalNum()] = sensor.getTarData().get((frame[sensor.getLogicalNum()]) & 255);
                sums[0] = sums[0] + frBD[sensor.getLogicalNum()];
            } else {
                int i = 0;
                for (Outline outline : cfg.getOutlines()) {
                    if (sensor.getOutline() == outline) {
                        frBD[sensor.getLogicalNum()] = sensor.getTarData().get(frame[sensor.getLogicalNum()] & 255);
                        sums[i] = sums[i] + frBD[sensor.getLogicalNum()];
                    }
                    i++;
                }
            }
            // координаты кадра для бабочки 
            /*
             * Есть 3 величины для N датчиков:
            x - положение датчика по оси X
            y - положение датчика по оси Y
            d - давление на датчике
            и есть сумма давление по всем датчикам - maxd
            координата бабочки по X = ( x1*(d1/maxd) + x2*(d2/maxd) + ... + xi*(d/maxd) ) / N;
            координата бабочки по Y = ( y1*(d1/maxd) + y2*(d2/maxd) + ... + yi*(d/maxd) ) / N;
             */
            final int len = (int) Math.round(Math.sqrt((cfg.center_x_b - sensor.x) * (cfg.center_x_b - sensor.x) + (cfg.center_y_b - sensor.y) * (cfg.center_y_b - sensor.y)));
            sumX = sumX + (sensor.x - cfg.center_x_b) * frBD[sensor.getLogicalNum()];
            sumY = sumY + (sensor.y - cfg.center_y_b) * frBD[sensor.getLogicalNum()];
            maxD = maxD + frBD[sensor.getLogicalNum()];
            //if (maxD < frBD[sensor.getLogicalNum()]) {
            //    maxD = frBD[sensor.getLogicalNum()];
            //}
        }
        sumX = sumX / maxD + cfg.center_x_b;
        sumY = sumY / maxD + cfg.center_y_b;

        // подготавливаем sql-запрос на вставку из готовых весов frBD и сумм по контуру sums
        forFrame.append("(").append(exam.getId()).append(",").append(nframe);
        for (int i = 0; i < frBD.length; i++) {
            forFrame.append(",").append(frBD[i]);
        }
        for (int i = 0; i < 12; i++) {
            forFrame.append(",").append(sums[i]);
        }
        forFrame.append(",").append(sumX).append(",").append(sumY).append(",0)");
        nframe++;
    }
}
